
import Vue from 'vue';
import fetcher from '../screen/fetcher';
import batch from '../screen/batch';
import { TabItem, getMonitor, putScreenMonitor, Monitor, allTabs, putTab, deleteTab } from '../screen/monitor';
import busHolder from '../../vue/bus';
import { KEY_TAB_CHANGED, KEY_SCREEN_REFRESH, KEY_SCREEN_CREATED, KEY_SCREEN_UPDATED,
    KEY_SCREEN_DESTROYED } from '../screen/events';

/**
 * 向上层 component 发送 bodyloaded 事件
 */
Vue.component("content-wait", {
  template : `
<a-spin size="large" style="margin:120px auto;width: 100%">
</a-spin>
  `,
  data : function () {
    return {
    };
  },

  props : {
    sid : String,
    type : String,
    params : Object
  },

  mounted : function () {
    try {
      load(this.sid, this.type, this.params)
        .then(loadResult => this.$emit('bodyloaded', loadResult))
        .catch(e => console.error(e));
    } catch (e) {console.error(e);}
  }
});

class Context {
  /** @type string */
  template = null;
  /** @type function : () => Vue */
  getBus = busHolder.getBus;
  /** @type function : () => ScreenStatus */
  getStatus = (sid) => {
    return batch.getOne(sid == null ? this.sid : sid);
  };
  /** @type function */
  sendCommand = fetcher.sendCommand;
  TabItem = TabItem;
  Monitor = Monitor;
  assetPath = (path) => fetcher.assetPath(this.seriesName, path);
  commonAssetPath = (path) => fetcher.assetPath('common', path);
  fetchHtmlScript = (path) => {
    return fetcher.fetchHtmlScript(this.seriesName, path);
  };

  allTabs = () => allTabs();
  putTab = (...tabs) => {
    let ret = putTab(tabs);
    if (ret.length == 0) { return; }
    busHolder.getBus().$emit(KEY_TAB_CHANGED, null);
  };
  deleteTab = (...keys) => {
    let ret = deleteTab(keys);
    if (ret.length == 0) { return; }
    busHolder.getBus().$emit(KEY_TAB_CHANGED, null);
  };

  events = {
    KEY_SCREEN_REFRESH, KEY_SCREEN_CREATED, KEY_SCREEN_UPDATED, KEY_SCREEN_DESTROYED
  };

  /**
   * @param {string} sid 
   * @param {string} series 
   * @param {string} scene 
   * @param {string} template 
   */
  constructor(sid, series, scene, template) {
    this.sid = sid;
    this.seriesName = series;
    this.sceneName = scene;
    this.template = template;
    this.getStatus.bind(this);
  }
}

class LoadBundle {
  /**
   * @param {string} sid 
   * @param {string} key 
   */
  constructor(sid, key) {
    this.sid = sid;
    this.key = key;
    const status = batch.getOne(sid);
    this.series = status.series;
    this.scene = status.scene;
    this.monitorName = `context-${status.series}-${status.scene}`.toLowerCase();
    this.compName = this.monitorName + '-' + key.toLowerCase();
  }
}

/**
 * 加载屏幕
 * @param {string} sid
 * @param {string} type 'screen'
 * @param {Object} params
 * @returns {Promise<LoadBundle>}
 */
function load(sid, type, params) {
  // sid
  const ret = new LoadBundle(sid, params.key);

  let monitor = getMonitor(ret.monitorName);
  if (monitor != null) {
    // 说明已经加载过了, Vue.component 已经调用过了
    return addVueComponent(ret, monitor).then(_ => ret);
  }

  let htmlText = null;
  return fetcher.fetchMonitorComponent(ret.series, ret.scene)
    .then((res) => {
      // [0]:htmlText, [1]:jsText, [2]:manifestText, [3]:jsEntry
      htmlText = res[0];
      let jsText = res[1];
      if (jsText.startsWith('var ')) {
        jsText = jsText.substring(4);
      }
      // 这里做一层转换.
      // 比如计时器 timer/timer 的 jsEntry 里面写的是 'monitor/t'
      // 那返回的是 js 加载路径是 ./comps/timer/monitor/t
      return loadModule(jsText, res[2], `./comps/${ret.series}/${res[3]}`);
    }).then((module) => {
      monitor = putScreenMonitor(ret.monitorName, module);
      return addVueComponent(ret, monitor, htmlText)
          .then(_ => ret);
    });
}

/**
 * @param {string} jsText 
 * @param {object} manifest 
 *   示例是: {"name":"chunk","content":{"${jsEntry}":{"id":7,...}}}
 * @param {string} jsEntry 
 * @returns {function | null}
 */
function loadModule(jsText, manifest, jsEntry) {
  var chunk = null;
  eval(jsText); // 这里, chunk 就不为 null 了

  /*
   * 按 development 方式打包, chunk 的获取方式是路径;
   * 按 production 方式则是自定义的数值, 只能靠 manifest 来读取: 如果没有 manifest 则不能以这个方式读
   */
  if (manifest == null) {
    let m = chunk(jsEntry); // throwable
    return m.default == null ? m : m.default;
  }

  let realEntry = manifest.content[jsEntry].id;
  let m = chunk(realEntry);
  return m.default == null ? m : m.default;
}

const compReg = {};

/**
 * @param {LoadBundle} bundle 
 * @param {Monitor} monitor 
 * @param {string} htmlText 一般只有 key='main' 时有这个
 */
function addVueComponent(bundle, monitor, htmlText = '') {
  const { compName, sid, series, scene, key } = bundle;

  if (compReg[compName]) {
    return Promise.resolve();
  }
  let comp0 = monitor.factory(new Context(sid, series, scene, htmlText), key);
  let promise = (comp0 instanceof Promise) ? comp0 : Promise.resolve(comp0);
  return promise.then(comp => {
    Vue.component(compName, comp);
    console.info(`component ${compName} loaded.`);
    compReg[compName] = true;
  });
}
